import Module from "./Module"
import * as THREE from "three"
import geoFT from "../math/geoFT"

import funcUtil from "../utils/func.util"

import DataSyncStore from "../storage/DataSyncStore"
import MapMonitor from "../collection/MapMonitor"
import aggregationTypeEnum from "../enum/aggregationType.enum"
import Logger from "../utils/Logger"
import { RemoveMattertagCommand, GetDiscPositionsCommand, AddMattertagCommand, EditMattertagCommand } from "../command/mattertag.command"
import MattertagsData from "../data/MattertagsData"
import NavigationUrl from "../utils/NavigationUrl"
class DescriptionParser {
  modelSID: any
  capabilites: { links: { enable: any; keepLabels: any } }
  constructor(e) {
    this.modelSID = e.model
    this.capabilites = {
      links: { enable: e.supportLinks, keepLabels: e.keepLinkLabels }
    }
  }
  parse(e) {
    const t = []
    if (!e) return []
    return (
      e
        .split("[")
        .map(function (e, t) {
          return 0 === t ? e : "[" + e
        })
        .forEach(e => {
          const i = this.findLink(e)
          i ? -1 === i.url.indexOf("javascript:") && (this.addLinkChunk(t, i), this.addTextChunk(t, e.slice(i.markdown.length))) : this.addTextChunk(t, e)
        }),
      t
    )
  }
  findLink(e) {
    const t = e.match(/\[([^\]]*)\]\((.*)\)/)
    if (!t) return null
    const i = t[2]
    let n = 1,
      s = 0
    for (; s < i.length && ("(" === i[s] ? n++ : ")" === i[s] && n--, 0 !== n); ) s++
    const r = i.length - s
    return {
      markdown: t[0].substring(0, t[0].length - r),
      label: t[1],
      url: this.conditionallyEncode(t[2].substring(0, s))
    }
  }
  conditionallyEncode(e) {
    return this.needsEncoding(e) ? encodeURI(e) : e
  }
  needsEncoding(e) {
    if (e.match(/([#\s\{\}\"\']|[^\x00-\x7F]|%[^0-9a-fA-F]{2})+/g)) return !0

    return !1
  }
  addTextChunk(e, t) {
    0 !== t.length && e.push({ type: TagDescriptionChunkType.text, text: t })
  }
  addLinkChunk(e, t) {
    if (!this.capabilites.links.enable) return void (this.capabilites.links.keepLabels && this.addTextChunk(e, t.label))
    const i: any = { label: t.label, url: t.url, type: TagLinkTypeMin.EXT_LINK }
    i.url.indexOf("://") === -1 && (i.url = "http://" + i.url)
    const n = -1 !== i.url.indexOf("3dyunzhan.com/show")
    const s = -1 !== i.url.indexOf(window.location.host + "/show")
    if (n || s) {
      const e = -1 !== i.url.indexOf(this.modelSID)
      const t = NavigationUrl.deserialize(i.url)
      e && t ? ((i.type = TagLinkTypeMin.NAVIGATION), (i.navigationData = t)) : ((i.type = TagLinkTypeMin.MODEL), (i.url += "&play=1"))
    }
    e.push({ type: TagDescriptionChunkType.link, link: i })
  }
}

var B = {
  LINK: "link",
  PHOTO: "photo",
  VIDEO: "video",
  RICH: "rich"
}

const V = new Logger("mattertags")
const deserialize = (e: Record<string, mattertagItem>) => {
  if (!e) return null
  const i: Record<string, Mattertag> = {}
  for (const n in e) {
    const r = e[n]
    try {
      const e = new Mattertag()
      e.sid = r.sid
      e.created = getDate(r.created)
      e.modified = getDate(r.modified)
      e.enabled = r.enabled
      e.label = r.label || ""
      e.description = r.description || ""
      e.anchorPosition = new THREE.Vector3(r.position.x, r.position.y, r.position.z)
      funcUtil.isNullOBJ(r.normal)
        ? V.debug("Ignored empty Mattertag normal", r.normal)
        : (e.anchorNormal = new THREE.Vector3(r.normal.x, r.normal.y, r.normal.z))
      e.stemVisible = void 0 === r.stem.enabled || r.stem.enabled
      e.floorId = "number" == typeof r.floor ? String(r.floor) : ""
      e.color = r.color ? new THREE.Color(r.color) : new THREE.Color("#03687d")
      r.media
        ? ((e.mediaType = TagDescriptionChunkType.parse(r.media.type)), (e.mediaSrc = r.media.src))
        : ((e.mediaType = TagDescriptionChunkType.none), (e.mediaSrc = ""))
      if (r.stem?.direction) {
        e.stemVector = new THREE.Vector3(r.stem.direction.x, r.stem.direction.y, r.stem.direction.z)
      } else {
        e.stemVector = e.anchorNormal.clone().setLength(geoFT.getM(1))
        V.debug(`Ignored missing stem direction for ${r.sid}`)
      }
      e.stemHeight = e.stemVector.length()
      i[n] = e
    } catch (e) {
      V.error("Failed to parse tag: " + r.sid, e)
    }
  }
  return i
}
const G = e => {
  const t = parseInt(e.floorId, 10)
  if (isNaN(t)) throw new Error(`Failed to serialize unsupported floor ID '${e.floorId}'`)
  const i: any = {
    sid: e.sid,
    created: dateISO(e.created),
    modified: dateISO(e.modified),
    enabled: e.enabled,
    label: e.label,
    description: e.description,
    color: funcUtil.createColorString(e.color),
    position: funcUtil.copyPostion(e.anchorPosition),
    normal: funcUtil.copyPostion(e.anchorNormal),
    floor: t,
    stem: {
      direction: funcUtil.copyPostion(e.stemVector),
      enabled: void 0 === e.stemVisible || e.stemVisible
    }
  }
  var n
  n = e.mediaType
  Object.values(B).includes(n) && (i.media = { type: e.mediaType, src: e.mediaSrc })
  return i as mattertagItem
}
const serialize = (e: Mattertag, t) => {
  const i: Record<string, mattertagItem> = {}
  const n = funcUtil.deepCopy(e)
  funcUtil.merge(n, t)
  for (const e of Object.keys(n)) {
    const t = n[e]
    t && (i[t.sid] = G(t))
  }
  return i
}

import Store from "../storage/Store"
import { CollectionSync } from "../storage/DataSync"
import CameraData from "../data/CameraData"
import $ from "../math/27687"
import SymbolList from "../SymbolList"
import Mattertag from "../object/Mattertag"
import { TagLinkTypeMin } from "../enum/tagLinkType.enum"
import { TagDescriptionChunkType } from "../enum/tagChunkType.enum"
import { dateISO, getDate } from "../utils/date.util"
import dataTypeEnum from "../enum/dataType.enum"
import EngineContext from "../EngineContext"
import ObservableMap from "../observer/observable.map"
import MarketContext from "../MarketContext"
import MeshQueryModule from "./MeshQueryModule"
const te = new Logger("mattertag-data")

export interface mattertagItem {
  modified?: string
  normal: mattertagPosition
  position: mattertagPosition
  sid: string
  stem: mattertagStem
  media: mattertagMedia
  label: string
  color: string
  created: string
  enabled: boolean
  description: string
  floor: number
}
export interface mattertagMedia {
  type: string
  src: string
}
export interface mattertagPosition {
  x: number
  y: number
  z: number
}
export interface mattertagStem {
  direction: mattertagPosition
  enabled: boolean
}

export default class MattertagDataModule extends Module {
  monitor: MapMonitor<ObservableMap<Mattertag>> | null
  getDiscPositions: (r: any) => Promise<any[]>
  data: MattertagsData
  addTag: (e: any) => Promise<string>
  editTag: (e: any) => Promise<void>
  descParser: DescriptionParser
  removeTag: (e: any) => Promise<any>
  engine: EngineContext
  config: any
  store: DataSyncStore<Record<string, Mattertag>>
  meshQueryModule: MeshQueryModule
  constructor() {
    super(...arguments)
    this.name = "mattertag-data"
    this.monitor = null
    this.getDiscPositions = (() => {
      const e: Array<{
        sid: string
        screen: THREE.Vector2
        world: THREE.Vector3
      }> = []
      const t: Record<string, THREE.Vector2> = {}
      const i: Record<string, THREE.Vector3> = {}
      const n = new THREE.Vector3()
      e.length = 0
      return async r => {
        r.tags.forEach((r: string) => {
          const o: Mattertag = this.data.getTag(r)
          o &&
            (i[r] || (i[r] = new THREE.Vector3()),
            i[r].copy(o.anchorPosition).add(o.stemVector),
            t[r] || (t[r] = new THREE.Vector2()),
            $.q9(this.cameraData, i[r], t[r], n),
            e.push({
              sid: r,
              screen: n.z > 1 ? null : t[r],
              world: i[r]
            }))
        })
        return e
      }
    })()
    this.addTag = async e => {
      const t = new Mattertag()
      let i = funcUtil.randomStr(11)
      for (; this.data.getTag(i); ) i = funcUtil.randomStr(11)
      t.sid = i
      this._editTag(t, e)
      this.data.addTag(t)
      return i
    }
    this.editTag = async e => {
      const t = this.data.getTag(e.sid)
      t && this._editTag(t, e.options)
    }
    this._editTag = (e: Mattertag, t) => {
      t.color && e.color.copy(t.color)
      t.enabled && (e.enabled = t.enabled)
      t.stemHeight && ((e.stemHeight = t.stemHeight), e.stemVector.copy(e.anchorNormal).setLength(t.stemHeight))
      t.stemVisible && (e.stemVisible = t.stemVisible)
      t.label && (e.label = t.label.slice())
      t.description && ((e.description = t.description), (e.parsedDescription = this.descParser.parse(e.description)))
      t.floorId && (e.floorId = t.floorId)
      t.mediaType && (e.mediaType = t.mediaType)
      t.mediaSrc && (e.mediaSrc = t.mediaSrc.slice())
      t.normal && (e.anchorNormal.copy(t.normal).normalize(), e.stemVector.copy(t.normal).setLength(e.stemHeight))
      t.position && e.anchorPosition.copy(t.position)
      e.modified = new Date()
      e.commit()
    }
    this.removeTag = async e => {
      if (this.data.getTag(e.sid) && this.data.removeTag(e.sid)) return e.sid
    }
  }
  cameraData: CameraData
  _editTag: any
  async init(e, t: EngineContext) {
    this.engine = t
    this.config = e
    this.descParser = new DescriptionParser({
      model: e.modelId,
      supportLinks: !0,
      keepLinkLabels: !0
    })
    this.store = this.configureStorage(e)
    const i = (await this.store.read()) || {}
    this.meshQueryModule = await t.getModuleBySymbol(SymbolList.MESH_QUERY)
    this.cameraData = await t.market.waitForData(CameraData)
    this.data = t.market.tryGetData(MattertagsData) || new MattertagsData()
    this.initializeTagData(i, t.market, e.parserOptions)
    this.monitor = this.configureMonitor(e, this.store, this.data)
    this.bindings.push(
      t.commandBinder.addBinding(GetDiscPositionsCommand, this.getDiscPositions),
      t.commandBinder.addBinding(AddMattertagCommand, this.addTag),
      t.commandBinder.addBinding(EditMattertagCommand, this.editTag),
      t.commandBinder.addBinding(RemoveMattertagCommand, this.removeTag)
    )
    this.configureTrash()
  }
  async revert() {
    if (this.store instanceof DataSyncStore) return this.store.revert()
  }

  clearDiffRecord() {
    this.monitor && this.monitor.clearDiffRecord()
  }
  configureStorage(e) {
    const { modelId, baseUrl, queue, storageProvider } = e
    te.info(`Loading Mattertags from ${storageProvider}`)

    if (!serialize || !deserialize) throw new Error("Mattertag sync requires serializer and deserializer")
    // const a = `${r}/api/v1/jsonstore/model/mattertags/${t}`
    const path = `${baseUrl}/scene-portal/${modelId}/api/mattertags`
    const publishStores = new Store.ReadOnlyStore({ queue, path, deserialize })
    const sync = new CollectionSync({ publishStores: [publishStores] })
    return new DataSyncStore(sync, {
      dataType: dataTypeEnum.MATTERTAGS,
      readonly: !0,
      engine: this.engine
    })
  }
  configureMonitor(e, t: MattertagDataModule["store"], i: MattertagsData) {
    const { readonly } = e
    if (!readonly) {
      const e = new MapMonitor(i.collection, {
        aggregationType: aggregationTypeEnum.Manual,
        shallow: !0
      })
      t instanceof DataSyncStore && t.addMonitor(e)
      return e
    }
    return null
  }
  initializeTagData(e: Record<string, Mattertag>, t: MarketContext, i: any) {
    const n = i ? new DescriptionParser(i) : this.descParser
    for (const t in e) {
      const i = e[t]
      i.parsedDescription = n.parse(e[t].description)
      this.meshQueryModule.inferMeshIdsFromPoint(i, i.anchorPosition)
      i.commit()
      this.data.addTag(i)
    }
    t.register(this, MattertagsData, this.data)
  }

  async configureTrash() {
    return
  }
}
